package study.javaee.practice.filter;

import cn.hutool.crypto.CryptoException;
import cn.hutool.crypto.symmetric.SymmetricCrypto;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import io.netty.util.internal.StringUtil;
import study.javaee.practice.common.api.CommonResult;
import study.javaee.practice.common.api.ResultCode;
import study.javaee.practice.common.exception.ApiException;
import study.javaee.practice.property.SecurityProperty;
import study.javaee.practice.service.AsymEncService;
import study.javaee.practice.service.SymEncService;

import javax.crypto.SecretKey;
import javax.servlet.ServletInputStream;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;

public class MyRequestParamsWrapper extends HttpServletRequestWrapper {

    private static AsymEncService asymEncService;
    private static SymEncService symEncService;
    private static SecurityProperty securityProperty;

    static void init(AsymEncService asymEncService, SymEncService symEncService, SecurityProperty securityProperty){
        MyRequestParamsWrapper.asymEncService = asymEncService;
        MyRequestParamsWrapper.symEncService = symEncService;
        MyRequestParamsWrapper.securityProperty = securityProperty;
    }

    Map<String, String[]> paramsMapper;

    /**
     * Constructs a request object wrapping the given request.
     *
     * @param request The request to wrap
     * @throws IllegalArgumentException if the request is null
     */
    public MyRequestParamsWrapper(HttpServletRequest request) {
        super(request);
        paramsMapper = new HashMap<>();
        paramsMapper.putAll(request.getParameterMap());
        parseData();
    }

    private void parseData(){
        decSymData();
        decAsymData();
    }

    private void decSymData(){
        String data = getParameter(securityProperty.getSymKeyAttrName());
        if (!StringUtil.isNullOrEmpty(data)){
            SymmetricCrypto crypto = (SymmetricCrypto)super.getSession().getAttribute(securityProperty.getSymKeyAttrName());
            if (crypto!=null) {
                try{
                    String parsedData = symEncService.decryptToStr(data, crypto);
                    pubParams(parsedData);
                }catch (Exception e){
                    throw new  ApiException(ResultCode.KEY_INVALID);
                }
            }
        }
    }

    private void decAsymData() {
        String data = getParameter(securityProperty.getAsymKeyAttrName());
        if (!StringUtil.isNullOrEmpty(data)){
            try{
                String parsedData = asymEncService.decDataWithPrivateKey(data);
                pubParams(parsedData);
            }catch (Exception e){
                throw new  ApiException(ResultCode.KEY_INVALID);
            }
        }
    }

    private void pubParams(String data){
        if (JSONUtil.isJson(data)){
            JSONObject jsonObject = JSONUtil.parseObj(data);
            for (Map.Entry<String, Object> entry : jsonObject.entrySet()){
                addParam(entry.getKey(), entry.getValue());
            }
        }
    }

    private void addParam(String key, Object value){
        if (value instanceof String[]){
            paramsMapper.put(key, (String[]) value);
        }else if (value instanceof String){
            paramsMapper.put(key, new String[]{(String) value});
        }else {
            paramsMapper.put(key, new String[]{value.toString()});
        }
    }

    @Override
    public Map<String, String[]> getParameterMap() {
        return paramsMapper;
    }

    @Override
    public String getParameter(String name) {
        String[] values = paramsMapper.get(name);
        return (values==null||values.length==0)?null:values[0];
    }

    @Override
    public Enumeration<String> getParameterNames() {
        return Collections.enumeration(paramsMapper.keySet());
    }

    @Override
    public String[] getParameterValues(String name) {
        return paramsMapper.get(name);
    }
}
